from types import MethodType, FunctionType
import inspect
import os
import json
import time
import logging
import random

logging.basicConfig(level=logging.INFO, format='[%(levelname)s %(filename)s %(funcName)s:%(lineno)d] %(message)s')
log = logging.getLogger(__file__)


class InfoError(Exception):
    pass


def get_timetxt(timestamp=None, fmt='%Y-%m-%d %H:%M:%S'):
    if timestamp is None:
        timestamp = time.time()
    t = time.localtime(timestamp)
    return time.strftime(fmt, t)


def return_dict(obj, *keys):
    out = {}
    for key in keys:
        out[key] = obj.get(key)
    return out


def cut_data(data, test_num=50, old_split_path=None):
    # 切分数据为训练集、验证集、测试集   （如果有老的切分信息，则使用老的验证集和测试集的_id）
    if old_split_path and os.path.isfile(old_split_path):
        split_info = json.load(open(old_split_path))
        valid_ids = set(split_info['valid'])
        test_ids = set(split_info['test'])
        for dtype in ['train', 'valid', 'test']:
            log.info(f'split_info: {dtype} data has {len(split_info[dtype])}')
        train, valid, test = [], [], []
        for doc in data:
            if doc['_id'] in valid_ids:
                valid.append(doc)
            elif doc['_id'] in test_ids:
                test.append(doc)
            else:
                train.append(doc)
    else:
        random.shuffle(data)
        log.info(f'未提供split_info')
        valid = data[:test_num]
        test = data[test_num:test_num + test_num]
        train = data[test_num + test_num:]
    out = {'train': train, 'valid': valid, 'test': test}
    split_info = {'num': {}}
    for dtype in ['train', 'valid', 'test']:
        split_info[dtype] = []
        split_info['num'][dtype] = len(out[dtype])
        log.info(f'{dtype} data has {len(out[dtype])}')
        for doc in out[dtype]:
            split_info[dtype].append(doc['_id'])
    out['split_info'] = split_info
    return out


def code_path(obj):
    try:
        filename = inspect.getfile(obj)
        line_no = inspect.getsourcelines(obj)[-1]
    except:
        return obj.__name__
    return f'File "{filename}", line {line_no} >> {obj.__name__}'


def get_safe_args(fn, raw_args):
    # 获取一个函数能够传入的参数（去掉多余的值） 避免多余的参数导致报错
    if isinstance(fn, (FunctionType, MethodType)):
        fn = fn
    else:  # 如果是类，则fn=对象的初始化函数
        fn = fn.__init__
    full_args = inspect.getfullargspec(fn)
    if full_args.varargs:
        raise ValueError(f'{fn} 不支持数组参数={full_args.varargs}')
    if full_args.varkw:  # 具有字典参数时，可以输入任意参数，所以全部返回
        return raw_args
    else:
        return {k: v for k, v in raw_args.items() if k in full_args.args}


def save_score_to_html(score, path, width='1000px', height='280px'):
    import pyecharts.globals
    from pyecharts import options as opts
    page = pyecharts.charts.Page()
    labels = list(score['best']['train'].keys())
    keys = [str(i) for i in range(len(score['history']))]
    line_opt = opts.InitOpts(theme=pyecharts.globals.ThemeType.CHALK, width=width, height=height)
    if 'loss' in score['best']:
        line = pyecharts.charts.Line(init_opts=line_opt)
        line.set_global_opts(title_opts=opts.TitleOpts(title='train-loss'), legend_opts=opts.LegendOpts())
        line.add_xaxis(keys)
        for label in labels:
            values = [round(t['loss'], 6) for t in score['history']]
            line.add_yaxis(label, values, is_symbol_show=False)
        page.add(line)

    for dtype in ['train', 'valid', 'test']:
        line = pyecharts.charts.Line(init_opts=line_opt)
        line.set_global_opts(title_opts=opts.TitleOpts(title=f'性能指标-{dtype}'), legend_opts=opts.LegendOpts())
        line.add_xaxis(keys)
        for label in labels:
            values = [round(t[dtype][label], 4) for t in score['history']]
            line.add_yaxis(label, values, is_symbol_show=True)
        page.add(line)
    page.render(path)


if __name__ == '__main__':
    score = json.load(open('../data/score.json'))
    save_score_to_html(score, 'page.html')
